// FacebookProxy.cs
// Facebook/FrameworkWeb/Web
// Copyright (c) 2007, Nikhil Kothari. All Rights Reserved.
//

using System;
using System.Diagnostics;
using System.Web;
using Facebook.Service;
using Facebook.Service.Core;
using Facebook.Web.Configuration;

namespace Facebook.Web {

    // TODO: This should implement IHttpAsyncHandler - there is no reason
    //       to not always use async requests here.
    //       Something to do once FacebookService provides async APIs...

    /// <summary>
    /// This class implements an ASP.NET endpoint that serves as a server-side
    /// proxy for performing Facebook API requests on behalf of client-side
    /// code.
    /// Even though Facebook supports cross-domain requests (using JSONP for
    /// example), that would require sending the application secret to the client
    /// which is insecure; hence a server-side proxy.
    /// The proxy itself is simply a REST service. You send Facebook API parameters
    /// as form-url-encoded name/value pairs in a POST request, and send additional
    /// meta-information about the method being invoked, and the Facebook context
    /// information as HTTP headers (fbMethod and fbContext).
    /// </summary>
    public sealed class FacebookProxy : IHttpHandler {

        private const string GenericErrorMessage = "Invalid Facebook Proxy Request.";
        private const string FailedErrorMessage = "Failed Facebook Proxy Request.";

        #region Implementation of IHttpHandler
        bool IHttpHandler.IsReusable {
            get {
                return true;
            }
        }

        void IHttpHandler.ProcessRequest(HttpContext context) {
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            // The combination of requiring POST + required headers was chosen for
            // security reasons. It does a few things:
            // 1. Sensitive data does not get cached
            // 2. Requests cannot be made by virtue of doing GET requests
            //    (eg. point image tag at the service)
            // 3. Requests cannot be submitted use cross-site form posts
            //    (eg. point action of an HTML form at the service)
            //
            // This is also important because Facebook API requests are themselves POST requests.

            if ((String.CompareOrdinal(request.HttpMethod, "POST") != 0) ||
                (String.CompareOrdinal(request.ContentType, "application/x-www-form-urlencoded") != 0)) {
                throw new HttpException(500, GenericErrorMessage);
            }

            string method = request.Headers["fbMethod"];
            string fbContext = request.Headers["fbContext"];
            string appName = null;
            string sessionKey = null;
            string userID = null;
            string pageUserID = null;

            if (String.IsNullOrEmpty(fbContext) == false) {
                string[] fbContextParts = fbContext.Split(';');

                if (fbContextParts.Length == 4) {
                    appName = fbContextParts[0];
                    sessionKey = fbContextParts[1];
                    userID = fbContextParts[2];
                    pageUserID = fbContextParts[3];
                }
            }

            if (String.IsNullOrEmpty(appName) ||
                String.IsNullOrEmpty(sessionKey) ||
                String.IsNullOrEmpty(userID) ||
                String.IsNullOrEmpty(method)) {
                throw new HttpException(500, GenericErrorMessage);
            }

            // These should never come from (or rather at the very least secret should never
            // be sent to) the client. This is the reason why we have an app name, and
            // load the key and secret from server-side configuration.

            FacebookApplicationSettings appSettings = FacebookSection.GetApplication(appName);
            if (appSettings == null) {
                throw new HttpException(500, GenericErrorMessage);
            }

            FacebookService service = new FacebookService(appSettings.ApiKey, appSettings.Secret,
                                                          sessionKey, userID, pageUserID);

            FacebookRequest fbRequest = new FacebookRequest(service.Session);
            foreach (string parameter in request.Form) {
                fbRequest.Parameters[parameter] = request.Form[parameter];
            }

            FacebookResponse fbResponse = fbRequest.InvokeMethod(method);
            if (fbResponse.Status == FacebookResponseStatus.Succeeded) {
                response.ContentType = "text/json";
                response.Output.Write(fbResponse.RawResponse);
            }
            else {
                throw new HttpException(500, FailedErrorMessage);
            }
        }
        #endregion
    }
}
